Skip to content

chore/no-ref: v0.3.0 refactor and open source prep#1

Merged
stephen-kintsugi merged 8 commits into
mainfrom
chore/no-ref/v0.3.0-refactor
Feb 26, 2026
Merged

chore/no-ref: v0.3.0 refactor and open source prep#1
stephen-kintsugi merged 8 commits into
mainfrom
chore/no-ref/v0.3.0-refactor

Conversation

@stephen-kintsugi
Copy link
Copy Markdown
Collaborator

@stephen-kintsugi stephen-kintsugi commented Feb 23, 2026

Summary

  • Remove the autocommit checker and delegate validation to squawk's own .squawk.toml config
  • Add --diff-branch flag to skip migrations that already exist on a given branch
  • Fail the run when alembic upgrade --sql errors instead of silently skipping (GenerateSqlError)
  • Merge auto-tag.yml into ci.yml so tagging is gated on tests passing
  • Fix CI expression injection by moving the tag output to an env: block
  • Tighten branch name regex to reject leading hyphens and .. traversal, handle missing git binary
  • Add MIT license, CONTRIBUTING.md, pyproject.toml metadata (authors, keywords, classifiers)
  • Pin snok/install-poetry to SHA, bump pyrefly-pre-commit to 0.53.0
  • Document env.py execution and DATABASE_URL fallback in README Known Limitations
  • Use from module import name style for ast, configparser, and pytest imports

Jira

n/a -- internal open source prep

Tests

29 tests passing. New coverage for:

  • GenerateSqlError on alembic failure and missing binary
  • Branch traversal (..) rejection
  • Missing git binary in validate_branch

Document Checklist

  • README updated with Known Limitations, version pinning docs, test skip note
  • CONTRIBUTING.md added
  • LICENSE added (MIT)

Summary by CodeRabbit

  • New Features

    • Added --diff-branch option to lint only new migrations against a target branch.
    • Enhanced error handling and validation for migration processing.
  • Bug Fixes

    • Improved SQL generation robustness with better error reporting.
  • Documentation

    • Added contributing guidelines.
    • Expanded README with version pinning and diff-branch usage examples.
    • Added MIT license.
  • Chores

    • Updated to version 0.3.0.
    • Configured automated tagging and release workflows.
    • Updated squawk-cli dependency to >=2.0.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 23, 2026

Walkthrough

Inlines a version-consistency check into the main CI workflow (removing the standalone workflow), bumps package to 0.3.0, consolidates auto-tag/release behavior into CI changes (and removes separate auto-tag workflow), replaces autocommit-block AST linting with a --diff-branch CLI flow that skips files present on a target branch, removes the autocommit tests, and adds diff-branch and Squawk config tests plus README, metadata, and contributor docs updates.

Changes

Cohort / File(s) Summary
CI Workflows
\.github/workflows/ci.yml, \.github/workflows/version-check.yml, \.github/workflows/auto-tag.yml
Updated main CI: added "Check version consistency" step and new jobs (auto-tag/release) in ci.yml; removed standalone version-check.yml and deleted previous auto-tag.yml.
Package Metadata & Packaging
pyproject.toml, squawk_alembic/__init__.py
Bumped project version 0.2.00.3.0, added project metadata, tightened squawk-cli constraint to >=2.0, added scripts and tooling configs.
Hook CLI & Core Logic
squawk_alembic/hook.py
Removed autocommit-block AST linting; migrated to stdlib AST parsing; added --diff-branch handling, validate_branch, file_exists_on_branch, RevisionInfo, GenerateSqlError; changed SQL generation/error flow and per-file skipping when file exists on target branch.
Tests — Removed
tests/test_autocommit_check.py
Deleted comprehensive autocommit/concurrently detection test module.
Tests — Added/Updated
tests/test_main.py, tests/test_squawk_config.py, tests/test_find_migrations_path.py, tests/test_revision_info.py
Added integration tests for Squawk config; extended test harness to simulate git behavior and added diff-branch tests; adjusted pytest fixture import/decorator style in some tests.
Documentation & Guides
README.md, CONTRIBUTING.md, LICENSE
README updated with pinning, --diff-branch guidance, limitations, and version/name updates; added CONTRIBUTING.md and MIT LICENSE.
Pre-commit & Ignore Rules
.pre-commit-config.yaml, .gitignore
Updated pyrefly hook rev/id and mode; significantly reduced .gitignore entries to a minimal set.

Sequence Diagram(s)

sequenceDiagram
    participant Hook as Hook (pre-commit)
    participant Git as Git
    participant Alembic as Alembic
    participant Squawk as Squawk CLI

    Note over Hook,Git: When --diff-branch is provided
    Hook->>Git: git rev-parse --verify <branch>
    alt branch exists
        Hook->>Git: git cat-file -e <branch>:<filepath>
        alt file exists on branch
            Git-->>Hook: exit 0 (exists)
            Hook-->>Hook: skip file
        else file missing on branch
            Git-->>Hook: exit !=0 (missing)
            Hook->>Alembic: generate SQL for migration file
            Alembic-->>Hook: SQL output
            Hook->>Squawk: run squawk on SQL
            Squawk-->>Hook: analysis result / exit code
        end
    else branch does not exist
        Git-->>Hook: exit !=0 (error)
        Hook-->>Hook: emit error and exit non-zero
    end
Loading
sequenceDiagram
    participant Hook as Hook (pre-commit)
    participant Alembic as Alembic
    participant Squawk as Squawk CLI

    Note over Hook,Alembic: When no --diff-branch
    Hook->>Alembic: generate SQL for each provided migration file
    Alembic-->>Hook: SQL output(s)
    Hook->>Squawk: run squawk on SQL
    Squawk-->>Hook: analysis result / exit code
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/test_squawk_config.py (1)

30-39: Add returncode == 0 assertion — the negative stdout check passes vacuously if squawk produces no output for any reason.

For ALTER TABLE foo ADD COLUMN bar text; with assume_in_transaction = true, squawk should find zero violations and exit 0. Without the returncode guard, a silent failure (empty stdout for any unexpected reason) makes the test a no-op.

✅ Proposed fix
     result = subprocess.run(
         ["squawk", str(sql_file)],
         capture_output=True,
         text=True,
         cwd=tmp_path,
     )
+    assert result.returncode == 0
     assert "prefer-robust-stmts" not in result.stdout
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_squawk_config.py` around lines 30 - 39, The test
test_with_assume_in_transaction_suppresses_prefer_robust_stmts should assert the
process exit status so a silent failure doesn't make the negative stdout check
vacuous; after running subprocess.run(...) add an assertion that
result.returncode == 0 (use the returned variable result) to ensure squawk
exited successfully before asserting "prefer-robust-stmts" is not in
result.stdout.
.github/workflows/ci.yml (1)

37-38: grep will abort the step before the friendly error message is reached if the pattern is ever not found.

GitHub Actions runs bash -eo pipefail by default, so a zero-match grep (exit 1) causes an immediate unclean failure at the assignment, bypassing the mismatch message entirely. Guarding with || true (or using || { echo "::error::..."; exit 1; }) keeps the diagnostic useful:

♻️ Suggested guard
-          PYPROJECT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
-          INIT_VERSION=$(grep '^__version__ = ' squawk_alembic/__init__.py | sed 's/__version__ = "\(.*\)"/\1/')
+          PYPROJECT_VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/' || true)
+          INIT_VERSION=$(grep '^__version__ = ' squawk_alembic/__init__.py | sed 's/__version__ = "\(.*\)"/\1/' || true)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 37 - 38, The two command substitutions
that set PYPROJECT_VERSION and INIT_VERSION can fail if grep finds no match,
causing the step to abort; modify the assignments for PYPROJECT_VERSION and
INIT_VERSION so the grep pipelines are guarded (e.g., append "|| true" to the
substitutions) and then explicitly check if the resulting variables are empty—if
empty, emit a clear GitHub Actions error (echo "::error::...") and exit 1;
update the code around the PYPROJECT_VERSION and INIT_VERSION assignments to
perform the guarded capture followed by the explicit error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 37-38: The two command substitutions that set PYPROJECT_VERSION
and INIT_VERSION can fail if grep finds no match, causing the step to abort;
modify the assignments for PYPROJECT_VERSION and INIT_VERSION so the grep
pipelines are guarded (e.g., append "|| true" to the substitutions) and then
explicitly check if the resulting variables are empty—if empty, emit a clear
GitHub Actions error (echo "::error::...") and exit 1; update the code around
the PYPROJECT_VERSION and INIT_VERSION assignments to perform the guarded
capture followed by the explicit error handling.

In `@tests/test_squawk_config.py`:
- Around line 30-39: The test
test_with_assume_in_transaction_suppresses_prefer_robust_stmts should assert the
process exit status so a silent failure doesn't make the negative stdout check
vacuous; after running subprocess.run(...) add an assertion that
result.returncode == 0 (use the returned variable result) to ensure squawk
exited successfully before asserting "prefer-robust-stmts" is not in
result.stdout.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 3d188ad and a43ab35.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • .github/workflows/version-check.yml
  • README.md
  • pyproject.toml
  • squawk_alembic/__init__.py
  • squawk_alembic/hook.py
  • tests/test_autocommit_check.py
  • tests/test_squawk_config.py
💤 Files with no reviewable changes (3)
  • .github/workflows/version-check.yml
  • squawk_alembic/hook.py
  • tests/test_autocommit_check.py

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/test_main.py (1)

221-294: Good coverage of the three diff-branch scenarios.

The three new tests cover the key paths:

  • Skip when file exists on branch (1 call)
  • Lint when file is new (3 calls)
  • No diff-branch flag means no git call (2 calls)

Call-count assertions and command-name checks provide reasonable confidence in the dispatch logic.

One minor observation: there's no test for when --diff-branch references a non-existent branch (e.g., --diff-branch nonexistent). In that case git cat-file -e returns non-zero, so the file would be linted — which is correct fail-open behavior. A test documenting this would be a nice addition but isn't critical.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_main.py` around lines 221 - 294, Add a test that covers the case
where --diff-branch names a non-existent branch: create a new test (e.g.,
test_diff_branch_nonexistent_branch_lints_file) mirroring
test_diff_branch_lints_new_file but pass "--diff-branch", "nonexistent" in
patched sys.argv and use patch("subprocess.run",
side_effect=fake_subprocess(git_exists_on_branch=False)) as mock_run; assert
main() == 0 and that mock_run.call_count == 3 (git cat-file + alembic + squawk)
and optionally assert the first subprocess call was "git" to document the
expected dispatch behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_main.py`:
- Around line 221-294: Add a test that covers the case where --diff-branch names
a non-existent branch: create a new test (e.g.,
test_diff_branch_nonexistent_branch_lints_file) mirroring
test_diff_branch_lints_new_file but pass "--diff-branch", "nonexistent" in
patched sys.argv and use patch("subprocess.run",
side_effect=fake_subprocess(git_exists_on_branch=False)) as mock_run; assert
main() == 0 and that mock_run.call_count == 3 (git cat-file + alembic + squawk)
and optionally assert the first subprocess call was "git" to document the
expected dispatch behavior.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between a43ab35 and 9b69a67.

📒 Files selected for processing (3)
  • README.md
  • squawk_alembic/hook.py
  • tests/test_main.py

Remove custom autocommit block checker and delegate to squawk's native
`assume_in_transaction` config in `.squawk.toml`. Add `--diff-branch`
flag so consumers can skip linting migrations that already exist on a
given branch. Consolidate version-check workflow into ci.yml. Clean up
gitignore to only entries relevant to this project.
@stephen-kintsugi stephen-kintsugi force-pushed the chore/no-ref/v0.3.0-refactor branch from 0d6c1a1 to 509d47b Compare February 23, 2026 23:04
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
squawk_alembic/hook.py (1)

202-227: ⚠️ Potential issue | 🟡 Minor

Minor temp-file leak if tmp.write(sql) raises.

The with block and try/finally are siblings, not nested. If tmp.write(sql) throws (e.g., disk full), the with block's __exit__ closes but does not delete the file (delete=False), and the finally is never reached, leaking the temp file. tmp_path itself is never unbound when finally runs (so no NameError), but the file goes undeleted.

In practice, writing a small SQL string to /tmp almost never fails, so this is low severity.

♻️ Suggested fix — nest the try/finally to cover the write
-        with tempfile.NamedTemporaryFile(mode="w", suffix=".sql", delete=False) as tmp:
-            tmp.write(sql)
-            tmp_path = tmp.name
-
-        try:
-            result = subprocess.run(
-                ["squawk", tmp_path],
-                capture_output=True,
-                text=True,
-            )
-            if result.returncode != 0:
-                output = result.stdout.replace(tmp_path, filepath)
-                error = result.stderr.replace(tmp_path, filepath)
-                if output:
-                    print(output)
-                if error:
-                    print(error, file=sys.stderr)
-                exit_code = 1
-        except FileNotFoundError:
-            print(
-                "squawk-alembic: squawk not found. Install with: pip install squawk-cli",
-                file=sys.stderr,
-            )
-            return 1
-        finally:
-            Path(tmp_path).unlink(missing_ok=True)
+        tmp_path = None
+        try:
+            with tempfile.NamedTemporaryFile(mode="w", suffix=".sql", delete=False) as tmp:
+                tmp.write(sql)
+                tmp_path = tmp.name
+            result = subprocess.run(
+                ["squawk", tmp_path],
+                capture_output=True,
+                text=True,
+            )
+            if result.returncode != 0:
+                output = result.stdout.replace(tmp_path, filepath)
+                error = result.stderr.replace(tmp_path, filepath)
+                if output:
+                    print(output)
+                if error:
+                    print(error, file=sys.stderr)
+                exit_code = 1
+        except FileNotFoundError:
+            print(
+                "squawk-alembic: squawk not found. Install with: pip install squawk-cli",
+                file=sys.stderr,
+            )
+            return 1
+        finally:
+            if tmp_path:
+                Path(tmp_path).unlink(missing_ok=True)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@squawk_alembic/hook.py` around lines 202 - 227, The temp-file can leak if
tmp.write(sql) raises because the finally that unlinks tmp_path is outside the
with; fix by assigning tmp_path = tmp.name immediately after opening the
NamedTemporaryFile (before writing) and then nest the try/finally that contains
the subprocess.run call and the Path(tmp_path).unlink(missing_ok=True) inside
the with block so the unlink runs even if tmp.write throws; adjust usage around
subprocess.run, tmp, tmp_path and Path.unlink accordingly.
🧹 Nitpick comments (1)
squawk_alembic/hook.py (1)

133-160: Optional: guard git subprocess calls against FileNotFoundError.

Both validate_branch and file_exists_on_branch call git without catching FileNotFoundError. In the pre-commit context, git is always present, so the practical risk is negligible. Still, an unhandled exception here would produce an ugly traceback rather than the clean error messages used elsewhere in the file.

♻️ Suggested guard (consistent with the existing alembic/squawk pattern)
 def validate_branch(branch):
     """Validate that a branch name is safe and exists in git."""
     if not _BRANCH_RE.match(branch):
         print(
             f"squawk-alembic: invalid branch name: {branch!r}",
             file=sys.stderr,
         )
         return False
-    result = subprocess.run(
-        ["git", "rev-parse", "--verify", branch],
-        capture_output=True,
-    )
+    try:
+        result = subprocess.run(
+            ["git", "rev-parse", "--verify", branch],
+            capture_output=True,
+        )
+    except FileNotFoundError:
+        print("squawk-alembic: git not found", file=sys.stderr)
+        return False
     if result.returncode != 0:
 def file_exists_on_branch(filepath, branch):
     """Check if a file exists on the given git branch."""
-    result = subprocess.run(
-        ["git", "cat-file", "-e", f"{branch}:{filepath}"],
-        capture_output=True,
-    )
-    return result.returncode == 0
+    try:
+        result = subprocess.run(
+            ["git", "cat-file", "-e", f"{branch}:{filepath}"],
+            capture_output=True,
+        )
+        return result.returncode == 0
+    except FileNotFoundError:
+        return False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@squawk_alembic/hook.py` around lines 133 - 160, Wrap the subprocess.run calls
in validate_branch and file_exists_on_branch with a try/except that catches
FileNotFoundError; on FileNotFoundError print a concise squawk-alembic error to
stderr (consistent with existing messages) and return False so the functions
fail gracefully instead of raising a traceback. Ensure you modify the
validate_branch function around the git rev-parse call and file_exists_on_branch
around the git cat-file call, preserving existing return semantics and using the
same stderr printing style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 36-49: Update the README section "Only lint new migrations" to
note that the --diff-branch value must be a locally resolvable git ref (or a
remote-ref like origin/main) because validate_branch uses git rev-parse --verify
and will fail for branches not present locally; mention the workaround of using
origin/main or running git fetch before the hook and reference the internal
symbol validate_branch (and that _BRANCH_RE already permits "/" so remote refs
like origin/main are accepted).

---

Outside diff comments:
In `@squawk_alembic/hook.py`:
- Around line 202-227: The temp-file can leak if tmp.write(sql) raises because
the finally that unlinks tmp_path is outside the with; fix by assigning tmp_path
= tmp.name immediately after opening the NamedTemporaryFile (before writing) and
then nest the try/finally that contains the subprocess.run call and the
Path(tmp_path).unlink(missing_ok=True) inside the with block so the unlink runs
even if tmp.write throws; adjust usage around subprocess.run, tmp, tmp_path and
Path.unlink accordingly.

---

Nitpick comments:
In `@squawk_alembic/hook.py`:
- Around line 133-160: Wrap the subprocess.run calls in validate_branch and
file_exists_on_branch with a try/except that catches FileNotFoundError; on
FileNotFoundError print a concise squawk-alembic error to stderr (consistent
with existing messages) and return False so the functions fail gracefully
instead of raising a traceback. Ensure you modify the validate_branch function
around the git rev-parse call and file_exists_on_branch around the git cat-file
call, preserving existing return semantics and using the same stderr printing
style.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 0d6c1a1 and 509d47b.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • .github/workflows/version-check.yml
  • .gitignore
  • README.md
  • pyproject.toml
  • squawk_alembic/__init__.py
  • squawk_alembic/hook.py
  • tests/test_autocommit_check.py
  • tests/test_main.py
  • tests/test_squawk_config.py
💤 Files with no reviewable changes (2)
  • .github/workflows/version-check.yml
  • tests/test_autocommit_check.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_squawk_config.py
  • .github/workflows/ci.yml

Comment thread README.md
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
squawk_alembic/hook.py (1)

202-227: ⚠️ Potential issue | 🟡 Minor

Temp file can leak if write fails before try/finally starts.

tmp_path is assigned after tmp.write(sql) (line 204). If the write raises (e.g., disk full), tmp_path is never set and the enclosing try/finally block hasn't begun, so Path(tmp_path).unlink(...) never runs — leaking the file created by delete=False.

Capturing tmp.name before the write and folding the with into the existing try block prevents both the variable-scope issue and the leak:

♻️ Proposed fix
-        with tempfile.NamedTemporaryFile(mode="w", suffix=".sql", delete=False) as tmp:
-            tmp.write(sql)
-            tmp_path = tmp.name
-
-        try:
+        tmp_path = None
+        try:
+            with tempfile.NamedTemporaryFile(mode="w", suffix=".sql", delete=False) as tmp:
+                tmp_path = tmp.name
+                tmp.write(sql)
             result = subprocess.run(
                 ["squawk", tmp_path],
                 capture_output=True,
                 text=True,
             )
             ...
         except FileNotFoundError:
             ...
             return 1
         finally:
-            Path(tmp_path).unlink(missing_ok=True)
+            if tmp_path:
+                Path(tmp_path).unlink(missing_ok=True)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@squawk_alembic/hook.py` around lines 202 - 227, The temp-file cleanup can
leak if tmp.write(sql) raises because tmp_path is set after the write and the
try/finally that unlinks tmp_path may never run; fix by capturing the temporary
filename before writing (tmp_path = tmp.name) and ensuring the with
NamedTemporaryFile block and the subprocess.run call are inside a single
try/finally so Path(tmp_path).unlink(missing_ok=True) always runs—i.e., assign
tmp_path immediately after creating tmp, perform tmp.write(sql) inside the try,
run subprocess.run (["squawk", tmp_path]) there, and keep the finally that
unlinks tmp_path; reference NamedTemporaryFile, tmp_path, tmp.write,
subprocess.run and the finally unlink call when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@squawk_alembic/hook.py`:
- Around line 133-160: Wrap the subprocess.run calls in both validate_branch and
file_exists_on_branch in a try/except FileNotFoundError block so a missing git
binary yields a clean stderr message and a safe boolean return instead of an
exception; specifically, in validate_branch catch FileNotFoundError, print a
message like "squawk-alembic: git not found" to sys.stderr and return False, and
in file_exists_on_branch do the same and return False on FileNotFoundError so
callers still get a boolean result.

---

Outside diff comments:
In `@squawk_alembic/hook.py`:
- Around line 202-227: The temp-file cleanup can leak if tmp.write(sql) raises
because tmp_path is set after the write and the try/finally that unlinks
tmp_path may never run; fix by capturing the temporary filename before writing
(tmp_path = tmp.name) and ensuring the with NamedTemporaryFile block and the
subprocess.run call are inside a single try/finally so
Path(tmp_path).unlink(missing_ok=True) always runs—i.e., assign tmp_path
immediately after creating tmp, perform tmp.write(sql) inside the try, run
subprocess.run (["squawk", tmp_path]) there, and keep the finally that unlinks
tmp_path; reference NamedTemporaryFile, tmp_path, tmp.write, subprocess.run and
the finally unlink call when making the change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 509d47b and 0c48a4e.

📒 Files selected for processing (5)
  • squawk_alembic/hook.py
  • tests/test_find_migrations_path.py
  • tests/test_main.py
  • tests/test_revision_info.py
  • tests/test_squawk_config.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_squawk_config.py

Comment thread squawk_alembic/hook.py
* add LICENSE (MIT), CONTRIBUTING.md, authors, keywords, classifiers
* merge auto-tag workflow into ci.yml gated on lint-and-test
* fix CI expression injection by moving tag to env block
* pin snok/install-poetry to SHA, bump pyrefly-pre-commit to 0.53.0
* tighten branch regex to reject leading hyphens and ".." traversal
* handle missing git binary in validate_branch
* fail the run when alembic upgrade --sql errors (GenerateSqlError)
* document env.py execution and DATABASE_URL fallback in README
* rename README title to squawk-pre-commit
* add .ruff_cache/ to .gitignore
@stephen-kintsugi stephen-kintsugi changed the title chore/no-ref: remove autocommit checker, bump to v0.3.0 chore/no-ref: v0.3.0 refactor and open source prep Feb 24, 2026
Copy link
Copy Markdown
Collaborator Author

@stephen-kintsugi stephen-kintsugi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review walkthrough of the key changes in this PR.

Comment thread squawk_alembic/hook.py
)


class GenerateSqlError(Exception):
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fail the run when alembic errors instead of silently skipping

generate_sql previously returned None on both legitimate skips (merge migrations, unparseable files) and actual failures (alembic not found, non-zero exit). Callers couldn't distinguish the two. Now legitimate skips still return None, but failures raise GenerateSqlError. The caller at line 211 catches it, prints to stderr, sets exit_code = 1, and continues linting the remaining files.

Comment thread squawk_alembic/hook.py

def validate_branch(branch):
"""Validate that a branch name is safe and exists in git."""
if not _BRANCH_RE.match(branch) or ".." in branch:
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Harden branch name validation against traversal and argument injection

The regex requires an alphanumeric first character (rejecting leading hyphens that could be interpreted as flags by git), and the ".." in branch check rejects path traversal sequences. Argparse already intercepts values starting with -- before we get here, but single-hyphen values like -bad would pass argparse and hit this check. Git's own check-ref-format also rejects .. in branch names, so this is defense in depth.

Comment thread squawk_alembic/hook.py
def main():
files = sys.argv[1:]
if not files:
parser = argparse.ArgumentParser()
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch from raw sys.argv to argparse

The old hook consumed sys.argv[1:] as filenames directly. Adding --diff-branch required proper argument parsing. Pre-commit passes --diff-branch <value> via args: in the consumer's .pre-commit-config.yaml, followed by the list of staged filenames.

Comment thread squawk_alembic/hook.py
file=sys.stderr,
)
return 0
return 1
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fail closed when alembic.ini is missing

Previously returned 0 (success) when alembic.ini couldn't be found. Since the hook can't function without it, this should be a hard failure so the user knows something is misconfigured.

Comment thread .github/workflows/ci.yml
run: poetry run pytest tests/ -v

auto-tag:
needs: lint-and-test
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gate auto-tag on CI passing

Previously auto-tag.yml and ci.yml were independent workflows both triggered on push: branches: [main]. A broken commit could get tagged and released before tests failed. Merging them into one workflow with needs: lint-and-test ensures tagging only happens after tests pass. The if: github.event_name == 'push' guard prevents the auto-tag job from running on PR checks.

Comment thread .github/workflows/ci.yml
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.auto-tag.outputs.tag }}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix expression injection in release step

The old workflow interpolated ${{ needs.auto-tag.outputs.tag }} directly in the shell run: block. If the tag output contained shell metacharacters, they would execute. Moving the value to an env: block and referencing it as $TAG prevents this because environment variables are not shell-interpolated.

* remove duplicate merge migration note from README Known Limitations
* preserve exception chain with `from exc` in generate_sql
* handle missing git binary in file_exists_on_branch
* add .env to .gitignore
@stephen-kintsugi stephen-kintsugi force-pushed the chore/no-ref/v0.3.0-refactor branch from 36a250e to 357179a Compare February 24, 2026 00:35
@mventnor-kintsugi
Copy link
Copy Markdown
Contributor

Run the squawk command with --assume-in-transaction so we get the most accurate rules

Alembic migrations always run inside a transaction by default, so squawk
should always be told to assume that context.
@stephen-kintsugi
Copy link
Copy Markdown
Collaborator Author

stephen-kintsugi commented Feb 24, 2026

The hook now always passes --assume-in-transaction to squawk since alembic migrations run inside a transaction by default.

@stephen-kintsugi
Copy link
Copy Markdown
Collaborator Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 24, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
squawk_alembic/hook.py (1)

135-169: LGTM — validate_branch and file_exists_on_branch handle missing git properly.

Both functions catch FileNotFoundError for the git binary. The control flow in main ensures validate_branch runs first (returning 1 on failure), so file_exists_on_branch's FileNotFoundError → False fallback is purely defensive and won't silently suppress lint.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@squawk_alembic/hook.py` around lines 135 - 169, No change required: both
validate_branch and file_exists_on_branch correctly handle a missing git binary
by catching FileNotFoundError; keep the existing try/except in validate_branch
and file_exists_on_branch as-is to ensure validate_branch still runs first and
returns 1 on failure while file_exists_on_branch remains defensive and returns
False when git is unavailable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@squawk_alembic/hook.py`:
- Around line 135-169: No change required: both validate_branch and
file_exists_on_branch correctly handle a missing git binary by catching
FileNotFoundError; keep the existing try/except in validate_branch and
file_exists_on_branch as-is to ensure validate_branch still runs first and
returns 1 on failure while file_exists_on_branch remains defensive and returns
False when git is unavailable.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 0c48a4e and e7c009e.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .github/workflows/auto-tag.yml
  • .github/workflows/ci.yml
  • .gitignore
  • .pre-commit-config.yaml
  • CONTRIBUTING.md
  • LICENSE
  • README.md
  • pyproject.toml
  • squawk_alembic/hook.py
  • tests/test_main.py
  • tests/test_squawk_config.py
💤 Files with no reviewable changes (1)
  • .github/workflows/auto-tag.yml
✅ Files skipped from review due to trivial changes (1)
  • LICENSE
🚧 Files skipped from review as they are similar to previous changes (2)
  • .gitignore
  • tests/test_squawk_config.py

@stephen-kintsugi stephen-kintsugi merged commit 7a8c0e3 into main Feb 26, 2026
4 checks passed
@stephen-kintsugi stephen-kintsugi deleted the chore/no-ref/v0.3.0-refactor branch February 26, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants